#!/usr/bin/perl

use strict;
use Device::SerialPort;
use Time::HiRes qw (time usleep);

my $if0 = new Device::SerialPort('/dev/ttyS0', 0);

$if0->baudrate(19200);
$if0->parity("odd");
$if0->databits(8);
$if0->stopbits(2);
$if0->handshake('none');

die "$!" unless $if0->write_settings;

my $if1 = new Device::SerialPort('/dev/ttyS1', 0);

$if1->baudrate(19200);
$if1->parity("odd");
$if1->databits(8);
$if1->stopbits(2);
$if1->handshake('none');

die "$!" unless $if1->write_settings;

# sensor reading blocky things.
my @sensors1 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,16);
#my @sensors0a = (17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,32);
#my @sensors0b = (33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,48);

my %sensor_addresses = (
			1 => 0,
			2 => 1,
			3 => 2,
			4 => 3,
			5 => 4,
			6 => 5,
			7 => 6,
			8 => 7,
			9 => 8,
			10 => 9,
			11 => 10,
			12 => 11,
			13 => 12,
			14 => 13,
			15 => 14,
			16 => 15,
);
##			17 => 0,
##			18 => 1,
##			19 => 2,
##			20 => 3,
##			21 => 4,
##			22 => 5,
##			23 => 6,
##			24 => 7,
##			25 => 8,
##			26 => 9,
##			27 => 10,
##			28 => 11,
##			29 => 12,
##			30 => 13,
##			31 => 14,
##			32 => 15,

##			33 => 16,
##			34 => 17,
##			35 => 18,
##			36 => 19,
##			37 => 20,
##			38 => 21,
##			39 => 22,
##			40 => 23,
##			41 => 24,
##			42 => 25,
##			43 => 26,
##			44 => 27,
##			45 => 28,
##			46 => 29,
##			47 => 30,
##			48 => 31,
##			);

my %sensor_groups = (
		     1 => [1,17,18],
		     2 => [2,19,20],
		     3 => [3,21,22],
		     4 => [4,23,24],
		     5 => [5,25,26],
		     6 => [6,27,28],
		     7 => [7,29,30],
		     8 => [8,31,32],
		     9 => [9,41,42],
		     10 => [10,35,36],
		     11 => [11,37,38],
		     12 => [12,39,40],
		     13 => [13,33,34],
		     14 => [14,43,44],
		     15 => [15,45,46],
		     16 => [16,47,48],
		     );

# invert the sensor_groups hash for quick lookups.
my %sensor_group_map = map { 
    my $group = $_;
    map { ($_ => $group); } @{$sensor_groups{$group}};
} sort keys %sensor_groups;

my %effect_addresses = (
		     1 => [32,33,34,35],
		     2 => [36,37,38,39],
		     3 => [40,41,42,43],
		     4 => [44,45,46,47],
		     5 => [48,49,50,51],
		     6 => [52,53,54,55],
		     7 => [56,57,58,59],
		     8 => [60,61,62,63],
		     9 => [64,65,66,67],
		     10 => [68,69,70,71],
		     11 => [72,73,74,75],
		     12 => [76,77,78,79],
		     13 => [80,81,82,83],
		     14 => [84,85,86,87],
		     15 => [88,89,90,91],
		     16 => [92,93,94,95],
		     );

# for storing the current state of each effect.
my %effect_state;

# store the sensor's current value.
my %sensor_current;

# how much above baseline to fire?
my $high_threshold = 1;

# calibrate the sensors.
my %baseline;
my %baseline_values;
$| = 1;
print "Calibrating...\n";
calibrate($if1, @sensors1);
print "uno!\n";
#calibrate($if0, @sensors0b);
#print "dos!\n";
#calibrate($if1, @sensors1);
#print "tres!\n";

print "averages:\n";
foreach my $sensor (sort { $a <=> $b } keys %baseline_values) {
  my $sum;
  foreach(@{$baseline_values{$sensor}}) {
    $sum += $_->{val};
  }
  
  $baseline{$sensor} = $sum / scalar(@{$baseline_values{$sensor}});
  print "$sensor: $baseline{$sensor}\n";
}

my %time_baseline;

# find out how long each sensor holds the high number.
foreach my $sensor (sort { $a <=> $b } keys %baseline_values) {
  my @high_blocks;  # not really necessary to build this, but oh well.
  my $last_val;
  my @current_block;
  foreach(@{$baseline_values{$sensor}}) {
    if(($last_val + $high_threshold) >= $baseline{$sensor}) {
      unless(($_->{val} + $high_threshold) >= $baseline{$sensor}) {
        # was in a block. now, not.
        push @high_blocks, \@current_block;
        undef @current_block;
#        print "block end\n";
      }
    }

    if(($_->{val} + $high_threshold) >= $baseline{$sensor}) {
      # in a block, save the value.
#      print "add $_->{val} ($baseline{$sensor})\n";
      push @current_block, $_;
    }
    
    $last_val = $_->{val};
  }

  my $high_time;
  # find the largest time.
  foreach my $block (@high_blocks) {
    next unless scalar(@{$block}) >= 2;
    my $this_time = $block->[(scalar(@{$block})) - 1]->{time} - $block->[0]->{time};
    $high_time = $this_time if($this_time > $high_time);
  }

  if($high_time < 0.060) {
    $time_baseline{$sensor} = 0.060;
  } else {
    $time_baseline{$sensor} = $high_time;
  }
  
  print "$sensor: $time_baseline{$sensor}\n";

}


#$baseline{5} += 6;
#$baseline{12} += 6;
#$baseline{6} += 6;
#$baseline{2} += 6;
#$baseline{29} += 6;
#$baseline{2} += 6;

print "\nDone.\n";

my $last_time = time();
my %timed;
my $exit = 0;
my @to_write;
my $last_batch = 'b';
while(!$exit) {
 # my @read_sensors0;
  my @read_sensors1 = @sensors1;
  
  # jankity...
 # if($last_batch eq 'a') {
 #   @read_sensors0 = @sensors0b;
 #   $last_batch = 'b';
 # } else {
 #   @read_sensors0 = @sensors0a;
 #   $last_batch = 'a';
 # }

  # translate sensor ids into sensor addresses.
 # my @write_sensors0 = map { $sensor_addresses{$_} } @read_sensors0;
  my @write_sensors1 = map { $sensor_addresses{$_} } @read_sensors1;
    
  # write whatever needs it between read cycles
  if(@to_write) {
    print "write: " . join(',', @to_write) . "\n";
    $if0->write(pack('C*', @to_write));
    $if0->read(255);
    undef @to_write;
  }

  # send the reads.
 # $if0->write(pack('C*', @write_sensors0));
  $if1->write(pack('C*', @write_sensors1));

 # pop @read_sensors0; #remove that last discarded value.
  pop @read_sensors1;

  my $first0 = 1;
  my $first1 = 1;
  
  while(@read_sensors1) {
 #   my ($count0, $raw_read0) = $if0->read(255);
    my ($count1, $raw_read1) = $if1->read(255);

    next unless ($count1);

    # jankity...
    my $pick = 1;
    foreach my $read ($raw_read1) {
      foreach my $value (unpack('C*', $read)) {
        my $curr;
        if($pick == 0) {
#          if($first0) {
#            undef $first0;
 #           next;
#          }

#          $curr = shift(@read_sensors0);
        } else {
          if($first1) {
            undef $first1;
            next;
          }

          $curr = shift(@read_sensors1);
        }

        push @{$timed{$curr}}, { val => $value, time => time() };
#        print $value . "\n" if $curr == 23;

        # trim @timed
        if(scalar(@{$timed{$curr}}) > 35) {
          shift(@{$timed{$curr}});
        } else {
          next;
        }
      
#        print "$curr: avg: $avg, count: " . scalar(@{$timed{$curr}}) . "\n" if $curr == 23;


        my $off = 0;
        foreach(reverse @{$timed{$curr}}) {
          unless($_->{val} >= ($baseline{$curr} + $high_threshold)) {
            # not high enough.
            $off = 1;
            last;
          }
          
          # have we made it?
          if(($_->{time} + $time_baseline{$curr}) <= time()) {
            # values stayed high for long enough, go ahead and fire.
            print "fire $curr, val $_->{val}, tdif " . (time() - $_->{time}) . ", bl $baseline{$curr}, time $time_baseline{$curr}\n";
            print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
            last;
          }
        }

        next;

        my $avg;
        my $first_time;
        $sensor_current{$curr} = $avg;


        # get the highest average in the group.
#        my @current = map { $sensor_current{$_} } @{$sensor_groups{$sensor_group_map{$curr}}};
#        my $measure = (sort { $b <=> $a } @current)[0];
        my $measure = $avg;
        
#        next if $avg > 75;  #skip the ones that aren't plugged in.
#        print "current: " . join(',', @current) . "\n";
        
        if($measure > $baseline{$curr} + 40) {
          print "low: $pick / $curr: avg: $measure, count: " . scalar(@{$timed{$curr}}) . ", time: " . sprintf('%.4f', ($last_time - $first_time)) . ", diff: " . ($measure - $baseline{$curr}) . ", baseline: $baseline{$curr}\n";
#          print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
          fire_the_fucking_flamethrowers_oh_my_god($sensor_group_map{$curr}, 'low');
        } elsif($measure > $baseline{$curr} + $high_threshold) {
          # was 11.5
          print "high: $pick / $curr: avg: $measure, count: " . scalar(@{$timed{$curr}}) . ", time: " . sprintf('%.4f', ($last_time - $first_time)) . ", diff: " . ($measure - $baseline{$curr}) . ", baseline: $baseline{$curr}\n";
#          print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
          fire_the_fucking_flamethrowers_oh_my_god($sensor_group_map{$curr}, 'high');
        } else {
#          print "off: $pick / $curr: avg: $measure, count: " . scalar(@{$timed{$curr}}) . ", time: " . sprintf('%.4f', ($last_time - $first_time)) . ", diff: " . ($measure - $baseline{$curr}) . ", baseline: $baseline{$curr}\n" if $avg > 45;
#          print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
          fire_the_fucking_flamethrowers_oh_my_god($sensor_group_map{$curr}, 'off');
        }
      }
      $pick = 1;
    }
  }
}

sub fire_the_fucking_flamethrowers_oh_my_god {
  my($effect, $state) = @_;

  # effect array offsets:
  # 0 - high off
  # 1 - high on
  # 2 - low off
  # 3 - low on
  
  if($effect_state{$effect} eq 'off') {
    if($state eq 'low') {
      push @to_write, $effect_addresses{$effect}->[3];
    } elsif($state eq 'high') {
      push @to_write, $effect_addresses{$effect}->[1];
    } else {
      # already off.
      #print "off from off\n";
    }
  } elsif($effect_state{$effect} eq 'low') {
    # low effect is on.
    if($state eq 'low') {
      # do nothing.
    } elsif($state eq 'high') {
      push @to_write, $effect_addresses{$effect}->[2];
      push @to_write, $effect_addresses{$effect}->[1];
    } else {
#      print "off from high\n";
      push @to_write, $effect_addresses{$effect}->[2];
    }
  } elsif($effect_state{$effect} eq 'high') {
    # high effect is on.
    if($state eq 'low') {
      push @to_write, $effect_addresses{$effect}->[0];
      push @to_write, $effect_addresses{$effect}->[3];
    } elsif($state eq 'high') {
      # do nothing.
    } else {
#      print "off from low\n";
      push @to_write, $effect_addresses{$effect}->[0];
    }
  }

  $effect_state{$effect} = $state;
}

sub calibrate {
  my ($if, @sensors) = @_;
  foreach(1..500) {
    my @read_sensors = @sensors;
    my @write_sensors = map { $sensor_addresses{$_} } @sensors;
    my $first = 1;

    $if->write(pack('C*', @write_sensors));
    pop @read_sensors;

    while(@read_sensors) {
      my ($count, $raw_read) = $if->read(255);
      foreach my $value (unpack('C*', $raw_read)) {
        if($first) {
          undef $first;
          next;
        }
        my $sensor = shift @read_sensors;
        push @{$baseline_values{$sensor}}, { val => $value, time => time() };
        print ".";
      }
    }
  }
}
